#!/usr/bin/python3
"""
Create YUM / DNF repo file in /etc/yum.repos.d

All repo-specific options, except the 'type' option, are supported. The 'type'
repo options is not supported, since it accepts only a single value, therefore
the ability to set it adds no value.

Only a subset of options which can be used in both, a repo or [main] section
configuration, is supported, specifically:
  - gpgcheck
  - repo_gpgcheck
"""

import os
import sys
import configparser

import osbuild.api


SCHEMA = r"""
"definitions": {
  "repo": {
    "type": "object",
    "additionalProperties": false,
    "oneOf": [
      {
        "required": ["id", "baseurl"]
      },
      {
        "required": ["id", "metalink"]
      },
      {
        "required": ["id", "mirrorlist"]
      }
    ],
    "description": "YUM / DNF repo definition.",
    "properties": {
      "id": {
        "type": "string",
        "description": "Repository ID.",
        "pattern": "^[\\w.\\-:]+$"
      },
      "baseurl": {
        "type": "array",
        "description": "List of URLs for the repository.",
        "minItems": 1,
        "items": {
          "type": "string",
          "minLength": 1
        }
      },
      "cost": {
        "type": "integer",
        "description": "The  relative  cost of accessing this repository, defaulting to 1000."
      },
      "enabled": {
        "type": "boolean",
        "description": "Include this repository as a package source."
      },
      "gpgkey": {
        "type": "array",
        "description": "URLs of a GPG key files that can be used for signing metadata and packages of this repository.",
        "minItems": 1,
        "items": {
          "type": "string",
          "minLength": 1
        }
      },
      "metalink": {
        "type": "string",
        "description": "URL of a metalink for the repository.",
        "minLength": 1
      },
      "mirrorlist": {
        "type": "string",
        "description": "URL of a mirrorlist for the repository.",
        "minLength": 1
      },
      "module_hotfixes": {
        "type": "boolean",
        "description": "Set this to True to disable module RPM filtering and make all RPMs from the repository available."
      },
      "name": {
        "type": "string",
        "description": "A human-readable name of the repository. Defaults to the ID of the repository.",
        "minLength": 1
      },
      "priority": {
        "type": "integer",
        "description": "The priority value of this repository."
      },
      "gpgcheck": {
        "type": "boolean",
        "description": "Whether to perform GPG signature check on packages found in this repository."
      },
      "repo_gpgcheck": {
        "type": "boolean",
        "description": "Whether to perform GPG signature check on this repository's metadata."
      }
    }
  }
},
"additionalProperties": false,
"description": "YUM / DNF repo file configuration.",
"properties": {
  "filename": {
    "type": "string",
    "pattern": "^[\\w.-]{1,250}\\.repo$",
    "description": "Repo file name."
  },
  "repos": {
    "type": "array",
    "description": "YUM / DNF repo definitions.",
    "minItems": 1,
    "items": {
        "$ref": "#/definitions/repo"
    }
  }
}
"""


# List of repo options which should be listed in this specific order if set
# in the stage options.
#
# Reasoning: repo configurations as shipped by distributions or created by
# various tools (COPR, RHSM) tend to order some options in a specific way,
# therefore if we just iterated over the dictionary items, the order would
# be different than how are repository configurations usually structured.
SPECIFIC_ORDER_OPTIONS = [
    "name",
    "baseurl",
    "metalink",
    "mirrorlist",
    "enabled",
    "gpgcheck",
    "repo_gpgcheck",
    "gpgkey"
]


def option_value_to_str(value):
    """
    Convert allowed types of option values to string.

    DNF allows string lists as a option value.
    'dnf.conf' man page says:
    "list   It is an option that could represent one or more strings separated by space or comma characters."
    """
    if isinstance(value, list):
        value = " ".join(value)
    elif isinstance(value, bool):
        value = "1" if value else "0"
    elif not isinstance(value, str):
        value = str(value)
    return value


def main(tree, options):
    filename = options.get("filename")
    repos = options.get("repos")

    yum_repos_dir = f"{tree}/etc/yum.repos.d"
    os.makedirs(yum_repos_dir, exist_ok=True)

    parser = configparser.ConfigParser()

    for repo in repos:
        repo_id = repo.pop("id")
        parser.add_section(repo_id)
        # Set some options in a specific order in which they tend to be
        # written in repo files.
        for option in SPECIFIC_ORDER_OPTIONS:
            option_value = repo.pop(option, None)
            if option_value is not None:
                parser.set(repo_id, option, option_value_to_str(option_value))

        for key, value in repo.items():
            parser.set(repo_id, key, option_value_to_str(value))

    # ensure that we won't overwrite an existing file
    with open(f"{yum_repos_dir}/{filename}", "x") as f:
        parser.write(f, space_around_delimiters=False)

    return 0


if __name__ == '__main__':
    args = osbuild.api.arguments()
    r = main(args["tree"], args["options"])
    sys.exit(r)
